Perjashtimet dhe trajtimi i tyre
Gjate shpjegimeve te deritanishme, here pas here kemi permendur faktin qe PHP-ja mund te gjeneroje gabime ne prani te situatave te ndryshme. Imagjinoni sikur nje funksion te caktuar i cili pranon nje argument te tipit array
ne hyrje, ne i kalojme nje argument te tipit string
. Cfare do te ndodhe ne kete moment? Normalisht, deri tani jemi mesuar me faktin qe PHP-ja gjeneron nje gabim. Koncepti eshte i drejte, por lind pyetja se cdo te thote te gjenerohet nje gabim nga PHP-ja?
Ajo qe ne te vertete ndodh ne momentet kur hasemi ne situata qe ka gabim, eshte qe PHP-ja leshon ne sistem nje objekt i cili quhet Perjashtim (ang. Exception). Tipi i perjashtimit qe leshohet ne sistem varet nga lloji i gabimit. P.sh. ne rastin e gabimit te mesiperm, PHP-ja leshon ne sistem nje objekt te tipit InvalidArgumentException
. E verteta eshte qe ekziston nje klase kryesore e brendshme e PHP-se e quajtur Exception
. Kjo klase eshte klase prind per nje numer te madh klasash te tjerash sic jane InvalidArgumentException
, OutOfBoundsException
, RuntimeException
, BadMethodCallException
etj. te cilat varen nga lloji i gabimit. Nje klase e pergjithshme e cila i perfshin te gjitha llojet e perjashtimeve eshte klasa Exception
. Kjo klase trashegohet nga shume nenklasa te cilat perfshijne gabime me specifikisht te ndare.
Kur ne ndonje pjese te kodit dicka shkon gabim, ne sistem leshohet nje perjashtim. Perjashtimet, ne momentin qe leshohen ne sistem, e ndalojne ekzekutimin e kodit ne ate pozicion qe u leshuan.
Disa lloje perjashtimesh, qe jane klase femije te superklases Exception
:
BadFunctionCallException
- Ne rast se therrasim nje funksion qe nuk ekziston, ose nuk kalojm numrin e duhur te argumenteve ne ndonje funksion atehere ne sistem leshohet nje perjashtim i ketij tipi.BadMethodCallException
- Ne rast se therrasim nje metode qe nuk ekziston te nje klase, ose nuk kalojm numrin e duhur te argumenteve ne ndonje metode atehere ne sistem leshohet nje perjashtim i ketij tipi.InvalidArgumentException
- Ne rast se ne nje funksion ose metode te caktuar, nuk kalojme tipin e duhur te argumentit sic e kerkon, atehere ne sistem leshohet nje perjashtim i ketij tipi.OutOfRangeException
- Ne rast se kerkojme te aksesojme nje indeks qe nuk ekziston p.sh. ne nje array, atehere ne sistem leshohet nje perjashtim i ketij tipi.
Klasa Exception
Se pse jemi te interesuar per metodat e klases Exception
do ta kuptojme ne fund te ketij seksioni, por fillimisht le te sqarojme metodat kryesore te saj.
Konstruktori i klases Exception
pranon argumentet e meposhtme:
<?php
function __construct($message = null, $code = 0, Exception $previous = null);
$message
- Cdo objektException
qe leshohet ne sistem permban nje mesazh, i cili pershkruan gabimin qe ndodhi.$code
- Cdo objektException
qe leshohet ne sistem permban nje kod numerik per llojin e gabimit.$previous
- Koncept pak me i avancuar, por sherben per rastet kur kemi perjashtime te nderfutura. Jep perjashtimin e pare qe shkaktoi perjashtimin aktual.
Ne rastin kur kemi nje objekt perjashtimi, variablat e mesiperme nuk mund ti aksesojme drejtperdrejt pasi nuk jan public
. Ajo cfare mund te aksesojme jane metodat getMessage()
dhe getCode()
te cilat na kthejne vleren e $message
dhe $code
respektivisht.
Si te leshojme nje perjashtim ne sistem
Parashtruam qe perjashtimet leshohen ne sistem nga vete ekzekutuesi i PHP-se, ose per lehtesi e themi nga vete PHP-ja. Perjashtime mund te leshojme edhe ne manualisht, ne kodin qe shkruajme. Perjashtimet leshohen duke perdorur termin throw
. Mjafton qe kete term ta shoqerojme me nje objekt te tipit Exception
, mund te jete edhe objekt i nje nenklase te saj.
Leshojme nje perjashtim ne nje skedar me emrin index.php
:
<?php
throw new Exception("Ne sapo hodhem nje Exception");
Nese ekzekutojme kodin e mesiperm do te na renderizohet mesazhi:
PHP Fatal error: Uncaught exception 'Exception' with message 'Ne sapo hodhem nje Exception' in /home/alban.afmeti/index.php:2
Stack trace:
#0 {main}
thrown in /home/alban.afmeti/index.php on line 2
Nese jeni hasur me perpara me gabime qe gjeneron PHP-ja, pak a shume eshte e njejta paraqitje si ajo, por me mesazhe te ndryshme. Le te provojme n.q.s. hedhja e nje perjashtimi do t'a ndaloje ekzekutimin e kodit apo jo:
<?php
echo "Fillojme ekzekutimin e skriptit PHP...<br><br>";
throw new Exception("Ne sapo hodhem nje Exception");
echo "Ky rresht nuk duhet te printohet!!!";
Pas ekzekutimit, perftojme rezultatin:
Fillojme ekzekutimin e skriptit PHP...
PHP Fatal error: Uncaught exception 'Exception' with message 'Ne sapo hodhem nje Exception' in /home/alban.afmeti/index.php:5
Stack trace:
#0 {main}
thrown in /home/alban.afmeti/index.php on line 5
Duke pare rezultatin, dallojme qarte qe rreshti Ky rresht nuk duhet te printohet!!!
nuk eshte printuar. Kjo verteton qe ne momentin e hedhjes se nje perjashtimi, ekzekutimi i skriptit nderpritet.
Perjashtime te pershtatura sipas nevojes
Ne mund te ndertojme vete nje tip perjashtimi, mjafton te trashegojme klasen baze Exception
. Perjashtimin e perashtasim sipas nevojave tona duke i vendosur nje mesazh apo kod numerik qe deshirojme.
Supozojme qe kemi nje funksion voto(...)
qe merr ne hyrje nje moshe, dhe pranon vetem moshat qe jane 18 vjec e lart. Ne rast te kundert funksioni do te leshoje nje perjashtim me nje mesazh te caktuar.
Ndertojme nje klase me emrin MosheEPalejuarException
. Kur ndertojme klasa per perjashtime te pershtatur, gjithnje ne fund te emrit eshte praktike e mire qe te vendosim prapashtesen Exception per te dalluar qe klasa ne fjale eshte e trasheguar nga klasa Exception
.
<?php
class MosheEPalejuarException extends Exception {
function __construct() {
parent::__construct("Mosha juaj nuk lejohet te votoje!!!", 1000);
}
}
Brenda funksionit __construct
, kemi therritur konstruktorin e klases prind me mesazhin Mosha juaj nuk lejohet te votoje!!!
, dhe me nje kod numerik 1000
.
Testojme perjashtimin e krijuar me ane te funksionit voto(...)
:
<?php
function voto(int $mosha) {
if($mosha < 18) {
throw new MosheEPalejuarException();
} else {
echo "Vota juaj kaloi me sukses!";
}
}
voto(23); // Rezultati: Vota juaj kaloi me sukses!
Ne rast se kalojme si argument nje numer me te madh se 17, do te printohet mesazhi Vota juaj kaloi me sukses!
. Ne rast se kalojme nje numer me te vogel se 18 p.sh. voto(17)
atehere do te leshohet perjashtimi dhe do te renderizohet mesazhi:
PHP Fatal error: Uncaught exception 'MosheEPalejuarException' with message 'Mosha juaj nuk lejohet te votoje!!!' in /home/alban.afmeti/index.php:6
Stack trace:
#0 {main}
thrown in /home/alban.afmeti/index.php on line 5
Supozojme qe kemi nje funksion connect()
i cili ben lidhjen me bazen e te dhenave. Ne rast se lidhja nuk eshte e mundur te beher pet ndonje arsye, PHP-ja do te leshoje nje objekt te tipit Exception
ne sistem. Supozojme qe objekti qe leshohet eshte i nenklases ConnectionException
.
<?php
echo "Fillojme ekzekutimin e skriptit PHP...<br><br>";
connect();
echo "Rreshti i fundit!!!";
Pasi ndodh leshimi i perjashtimit, skripti nderpritet ne menyre te menjehershme dhe rreshti Rreshti i fundit!!!
nuk printohet. Po sikur te duam qe ekzekutimi i skriptit te vazhdoje pavaresisht se ne sistem hidhet ky perjashtim? Kjo gje eshte e mundur nepermjet trajtimit te perjashtimeve.
Trajtimi i perjashtimeve
Trajtimi i perjashtimeve na vjen ne ndihme per menaxhuar perjashtimet e ndryshme qe hidhen ne sistem, qofshin ata te hedhur automatikisht nga PHP-ja ose te hedhur manualisht nga ne. Trajtimi i perjashtimeve behet nepermjet blloqeve try
- catch (...)
.
Brenda bllokut try
vendoset kodi qe duam te ekzekutohet dhe te menaxhohet njekohesisht duke pritur nga momenti ne moment per leshimin e ndonje perjashtimi. Blloku catch (...)
brenda kllapave rrethore merr si parameter nje objekt perjashtimi, tipin e perjashtimit qe eshte i afte te trajtoje (te menaxhoje). P.sh. catch (Exception $ex)
arrin te trajtoje te gjitha llojet e perjashtimeve sepse klasa Exception
eshte klasa prind per te gjitha perjashtimet. Ndersa catch (MosheEPalejuarException $ex)
arrin te trajtoje vetem perjashtime te tipit MosheEPalejuarException
.
Marrim nje shembull sesi funksionon blloku try
- catch
.
<?php
echo "Fillojme ekzekutimin e skriptit PHP...<br><br>";
try {
connect();
echo "Ne rast perjashtimi ky rresht nuk ekzekutohet!";
} catch(Exception $ex) {
echo "U hodh nje perjashtim! <br><br>";
}
echo "Rreshti i fundit!!!";
Brenda bllokut try
kemi futur pjesen e kodit qe dyshohet te leshoje ndonje perjashtim. Ne rast se gjate ekzekutimit te funksionit connect()
hidhet perjashtim nga PHP-ja, ekzekutimi i bllokut try
nderpritet, dhe stafeta i kalon bllokut catch
qe trajton perjashtimin e hedhur. Objekti perjashtim i hedhur nga PHP-ja, ne kete moment mund te aksesohet nepermjet references se parametrit te bllokut catch
, pra referenca $ex
. Me ane te kesaj reference mund te therrasim metoda te ndryshme te klases Exception
sic eshte getMessage()
, getCode()
etj. Kur blloku catch
merr stafeten, ekzekutohet i gjithe kodi brenda tij. Pasi perfundon ekzekutimi i bllokut catch
, ekzekutimi vazhdon me pjesen tjeter te skriptit qe vijon.
Ne rast se ne kodin e mesiperm leshohet nje perjashtim, do te renderizohen mesazhet e meposhtme:
Fillojme ekzekutimin e skriptit PHP...
U hodh nje perjashtim!
Rreshti i fundit!!!
Ne rast se ne kodin e mesiperm nuk leshohet nje perjashtim, do te renderizohen mesazhet e meposhtme:
Fillojme ekzekutimin e skriptit PHP...
Ne rast perjashtimi ky rresht nuk ekzekutohet!
Rreshti i fundit!!!
Pra, dallojme qarte qe nese nuk kemi leshim perjashtimi, blloku catch
injorohet.
Ne disa raste eshte i nevojshem me shume se nje bllok catch
pasi nuk jemi te sigurte se cfare lloj perjashtimesh mund te hidhen ne sistem.
Shembull:
<?php
function voto(int $mosha) {
if($mosha < 18) {
throw new MosheEPalejuarException();
} else {
echo "Vota juaj kaloi me sukses!";
}
}
try {
voto(15);
} catch(MosheEPalejuarException $ex) {
echo $ex->getMessage(); //Printojme mesazhin e vete perjashtimit
} catch(Exception $ex) {
echo $ex->getMessage(); //Printojme mesazhin e vete perjashtimit
}
Ne ndryshim nga shembulli i pare, brenda bllokut catch
printojme mesazhin e vete perjashtimit echo $ex->getMessage();
. Ne rast se perjashtimi qe leshohet nga funksioni voto(...)
eshte objekt i klases MosheEPalejuarException
, atehere ai do te trajtohet nga blloku i pare catch
. Ne te kundert, ne rast se perjashtimi qe leshohet eshte nje objekt i nje tipi tjeter, atehere ai trajtohet nga blloku i dyte catch
. Arsyeja pse behet kjo gje eshte sepse blloku i dyte catch
trajton perjashtimet e tipit Exception
qe eshte nje klase prind per te gjitha klasat e perjashtimeve, ndaj eshte i afte te trajtoje te gjitha llojet e perjashtimeve.
Duhet te kemi kujdes, sepse ne momentin e leshimit te perjashtimit, blloqet catch
skanohen me radhe nga i pari deri tek i fundit per te gjetur tipin qe trajton perjashtimin e hedhur.
Situata e meposhtme eshte gabim:
<?php
try {
voto(15);
} catch(Exception $ex) {
echo $ex->getMessage();
} catch(MosheEPalejuarException $ex) {
echo $ex->getMessage();
}
Gabimi qendron ne faktin qe blloku i dyte catch
nuk do te ekzekutohet asnjehere ne jete, pavaresisht llojit te perjashtimit qe hidhet. Edhe n.q.s. hidhet perjashtim i tipit MosheEPalejuarException
, fillon skanimi per te gjetur bllokun catch
qe eshte i afte te trajtoje kete perjashtim. Qe ne momentin e pare PHP-ja vlereson si te afte bllokun e pare catch
sepse ai pranon objekte te klases Exception
, e cila eshte klase prind per te gjitha llojet e perjashtimeve. Blloku i pare eshte i afte te trajtoje te gjitha llojet e perjashtimeve, per kete arsye blloqet e tjera catch
edhe sikur te ishin me shumice nuk do te ekzekutoheshin kurre.
Ky problem zgjidhet duke vendosur gjithmone klasat femije ne fillim, dhe klasat prind ne fund. Pra, hierarkia e vendosjes se blloqeve catch
duhet te nise nga femijet tek prinderit.
<?php
try {
voto(15);
} catch(MosheEPalejuarException $ex) {
echo $ex->getMessage();
} catch(Exception $ex) {
echo $ex->getMessage();
}
Ne rastin e mesiperm, hedhja e nje perjashtimi te tipit MosheEPalejuarException
do te trajtohet nga blloku i pare catch
sepse ai eshte blloku i pare ne liste i afte per te trajtuar nje perjashtim te tille.
Blloqet finally
Blloqet finally
jane vazhdim i blloqeve try
- catch
dhe vendosen ne fund te tyre.
<?php
try {
voto(15);
} catch(MosheEPalejuarException $ex) {
return 1;
}
echo "Rreshti i fundit!";
Ne shembullin e mesiperm, ne rast se leshohet nje perjashtim ekzekutohet blloku catch
i cili thjesht kthen vleren 1
me ane te deklarates return 1;
. Dukeqenese u perdor deklarata return
kodi nuk vazhdon te ekzekutohet me poshte, pra teksti Rreshti i fundit!
nuk printohet. Ne disa raste eshte i nevojshem ekzekutimi i nje pjese kodi, edhe pse brenda bllokut try
- catch
mund te kemi kthyer nje vlere me ane te deklarates return
. Na vjen ne ndihme blloku finally
:
<?php
try {
voto(15);
} catch(MosheEPalejuarException $ex) {
return 1;
} finally {
echo "Blloku finally ekzekutohet gjithmone! <br><br>";
echo "Rreshti i fundit!";
}
Kodi brenda bllokut finally
ekzekutohet gjithmone pavaresisht se cfare ndodh ne blloqet try
- catch
. Ndaj, rezultati pas ekzekutimit te kodit te mesiperm do te jete:
Blloku finally ekzekutohet gjithmone!
Rreshti i fundit!
Zakonisht blloku finally
perdoret per te mbyllur ndonje lidhje me databazen, me ndonje file te hapur etj. Vendoset brenda ketij blloku per te qene te sigurte qe lidhja do te mbyllet pavaresisht problemeve qe mund te lindin ne blloqet try
- catch
.
Rileshimi i perjashtimeve
Ndonjehere mund te jete i nevojshem rileshimi i perjashtimeve qe do te thote, ne e trajtojme nje perjashtim, shikojme pa informacion ne lidhje me te dhe e rihedhim perjashtimin perseri.
Shembull:
<?php
$filename = "tedhena.txt";
try {
$text = file_get_contents($filename); // Lexojme skedarin tedhena.txt
if ($text === false) { // Nqs. nuk arrijme te lexojme hedhim nje perjashtim
throw new Exception();
}
} catch (Exception $ex) {
if (!file_exists($filename)) { // Nqs. skedari nuk ekziston
throw new FileExistException($filename . " nuk ekziston.");
}
elseif (!is_readable($filename)) {
throw new FileReadException($filename . " nuk eshte i lexueshem."); //Nqs. skedari eshte i palexueshem
}
else {
throw new Exception("Gabim i panjohur per te aksesuar kete skedar.");
}
}
Ne shembullin e mesiperm perpiqemi te lexojme skedarin tedhena.txt
. Ne rast se leximi na kthen false
hedhim nje perjashtim manualisht throw new Exception();
. Ky perjashtim normalisht do te kapet dhe trajtohet nga blloku i pare catch
. Behen disa kontrolle brenda bllokut catch
. N.q.s. skedari nuk ekziston hidhet nje perjashtim i tipit FileExistException
me nje mesazh "tedhena.txt nuk ekziston.". N.q.s. skedari eshte i palexueshem hidhet nje perjashtim i tipit FileReadException
me nje mesazh "tedhena.txt nuk eshte i lexueshem.". Perndryshe hidhet nje perjashtim i tipit Exception
me mesazhin "Gabim i panjohur per te aksesuar kete skedar.". Ne kete rast kemi bere nje rileshim te perjashtimit. Kur rileshojme nje perjashtim duhet te jemi te sigurte qe perjashtimi i hedhur do te trajtohet diku tjeter, ne menyre qe mos te perfundoje ekzekutimin e skriptit me gabim fatal.